package org.dousb.doupicturebackend.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.extern.slf4j.Slf4j;
import org.dousb.doupicturebackend.manager.websocket.disruptor.PictureEditEventProducer;
import org.dousb.doupicturebackend.manager.websocket.model.PictureEditActionEnum;
import org.dousb.doupicturebackend.manager.websocket.model.PictureEditMessageTypeEnum;
import org.dousb.doupicturebackend.manager.websocket.model.PictureEditRequestMessage;
import org.dousb.doupicturebackend.manager.websocket.model.PictureEditResponseMessage;
import org.dousb.doupicturebackend.model.entity.User;
import org.dousb.doupicturebackend.service.UserService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


/**
 * websocket处理器
 * 连接成功 连接关闭 客户端消息通知
 */
@Component
@Slf4j
public class PictureEditHandler extends TextWebSocketHandler {

    // 每张图片的编辑状态，key: pictureId，value: 当前正在编辑的用户 ID
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();
    // 保存所有连接的会话，key: pictureId，value: 用户会话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    @Resource
    private UserService userService;

    @Resource
    @Lazy
    private PictureEditEventProducer pictureEditEventProducer;

    /**
     * 连接成功 发送消息通知
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        //保存会话到集合中
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        pictureSessions.get(pictureId).add(session);
        //构造响应 发送加入编辑的消息通知
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("用户 %s 加入编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userService.getUserVO(user));

    }

    /**
     * 接收客户端消息
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        //转换消息类型
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        //从session属性中 获取到公共参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");

        //根据消息类型进行不同的处理(生产消息到Disruptor队列去)
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);

    }

    /**
     * 处理进入编辑的消息
     *
     * @param session
     * @param pictureEditRequestMessage
     * @param user
     * @param pictureId
     */
    public void handleEnterEditMessage(WebSocketSession session, PictureEditRequestMessage pictureEditRequestMessage, User user, Long pictureId) throws IOException {
        //没有用户正在编辑 才能进入编辑状态
        Long existingEditor = pictureEditingUsers.putIfAbsent(pictureId, user.getId());
        if (existingEditor == null) {
            pictureEditingUsers.put(pictureId, user.getId());
            //构造响应 发送加入编辑的消息通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            String message = String.format("用户 %s 开始编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }

    /**
     * 处理编辑动作的消息
     *
     * @param session
     * @param pictureEditRequestMessage
     * @param user
     * @param pictureId
     */
    public void handleEditActionMessage(WebSocketSession session, PictureEditRequestMessage pictureEditRequestMessage, User user, Long pictureId) throws IOException {
        //正在编辑的用户
        Long editingUserId = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnum pictureEditActionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        if (pictureEditActionEnum == null) {
            log.info("无效的编辑动作");
            PictureEditResponseMessage errorMsg = new PictureEditResponseMessage();
            errorMsg.setType(PictureEditMessageTypeEnum.ERROR.getValue());
            errorMsg.setMessage("无效的编辑动作");
            session.sendMessage(new TextMessage(JSONUtil.toJsonStr(errorMsg)));
            return;
        }
        //确认是当前的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            //构造响应 发送编辑动作的消息通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            String message = String.format("用户 %s 进行了 %s", user.getUserName(), pictureEditActionEnum.getText());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setEditAction(editAction);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }

    /**
     * 处理退出编辑的消息
     *
     * @param session
     * @param pictureEditRequestMessage
     * @param user
     * @param pictureId
     */
    public void handleExitEditMessage(WebSocketSession session, PictureEditRequestMessage pictureEditRequestMessage, User user, Long pictureId) throws IOException {
        //正在编辑的用户
        Long editingUserId = pictureEditingUsers.get(pictureId);
        //确认是当前的编辑者
        if (editingUserId.equals(user.getId()) && editingUserId == null) {
            pictureEditingUsers.remove(pictureId);
            //构造响应 发送退出编辑的消息通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("用户 %s 退出编辑", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }

    /**
     * 前端退出 关闭资源
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        //从session属性中 获取到公共参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");

        //移除当前用户的编辑状态
        handleExitEditMessage(session, null, user, pictureId);
        //移除当前用户的会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (sessionSet != null) {
            sessionSet.remove(session);
            if (sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }
        //通知其他用户 离开了编辑状态
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
        String message = String.format("用户 %s 退出编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userService.getUserVO(user));
        broadcastToPicture(pictureId, pictureEditResponseMessage, session);
    }

    /**
     * 向指定图片的所有会话广播编辑响应消息
     *
     * @param pictureId                  图片ID，用于查找相关的WebSocket会话
     * @param pictureEditResponseMessage 要广播的消息内容
     * @param excludeSession             要排除的会话，通常为发送消息的源会话，避免回发给发送者
     * @throws IOException 当发送消息发生IO错误时抛出
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage
            pictureEditResponseMessage, WebSocketSession excludeSession) throws IOException {
        // 获取与指定图片ID关联的所有WebSocket会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        // 检查会话集合是否为空
        if (CollUtil.isNotEmpty(sessionSet)) {
            // 创建 ObjectMapper实例用于JSON序列化
            ObjectMapper objectMapper = new ObjectMapper();
            // 配置序列化：将 Long 类型转为 String，解决丢失精度问题
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);
            // 序列化为 JSON 字符串
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            for (WebSocketSession session : sessionSet) {
                // 排除掉的 session 不发送
                if (excludeSession != null && excludeSession.equals(session)) {
                    continue;
                }
                if (session.isOpen()) {
                    session.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 消息广播给所有的用户
     *
     * @param pictureId
     * @param pictureEditResponseMessage
     * @throws Exception
     */
    private void broadcastPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws
            Exception {
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);
    }
}
